﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.Composition;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Globalization;

namespace Steve
{
    public abstract class SiteExplorerRepository : AuditRepositoryBase
    {
        const string LogCategory = "Steve-SiteExplorerRepository";
        const string SitesKey = "Sites";
        const string NavKeyPrefix = "UrlCache_";

        [Import]
        public ILoggingService Log { get; set; }
        [Import]
        public IConfiguration Config { get; set; }
        [Import]
        public ICacheService Cache { get; set; }
        
        [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
        protected abstract IEnumerable<Site> GetSitesCore();
        
        [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
        public IEnumerable<Site> GetSites()
        {
            return Cache.GetValue<IEnumerable<Site>>(CacheType.Url, SitesKey, Config.DataCacheTtl, () => GetSitesCore());
        }

        public SiteUrl SaveSiteUrl(SiteUrl toSave)
        {
            if (toSave == null) throw new ArgumentNullException("toSave");

            if (toSave.Id == Guid.Empty) toSave.Id = Guid.NewGuid();
            
            ProcessAudit(toSave);
            
            var returnValue = SaveSiteUrlCore(toSave);
            
            Cache.Invalidate(CacheType.Url);

            return returnValue;
        }

        public RewriteUrl SaveUrlRewrite(RewriteUrl toSave)
        {
            if (toSave == null) throw new ArgumentNullException("toSave");

            if (toSave.Id == Guid.Empty) toSave.Id = Guid.NewGuid();
            ProcessAudit(toSave);
            var returnValue = SaveUrlRewriteCore(toSave);
            
            Cache.Invalidate(CacheType.Url);
            return returnValue;
        }

        public Site SaveSite(Site toSave)
        {
            if (toSave == null) throw new ArgumentNullException("toSave");

            ProcessAudit(toSave);
            var returnValue = SaveSiteCore(toSave);

            Cache.Invalidate(CacheType.Url);
            return returnValue;
        }

        public DirectoryNode<UrlObject, UrlObjectNodeMetadata> GetSiteDirectory(string siteName)
        {
            var key = CreateUrlCacheKey(siteName);
            return Cache.GetValue<DirectoryNode<UrlObject, UrlObjectNodeMetadata>>(CacheType.Url, key, Config.DataCacheTtl, () => BuildSiteDirectoryCache(siteName));
        }

        private static string CreateUrlCacheKey(string siteName)
        {
            return NavKeyPrefix + siteName;
        }

        //TODO: make this more lazy? maybe unnecessary...
        private DirectoryNode<UrlObject, UrlObjectNodeMetadata> BuildSiteDirectoryCache(string siteName)
        {
            Func<UrlObject, UrlObjectNodeMetadata> selectName = u =>
            {
                if (string.IsNullOrEmpty(u.Url) || !u.Url.StartsWith("/", StringComparison.Ordinal)) throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Url '{0}' has a null/empty url or does not start with a '/'", u));

                return new UrlObjectNodeMetadata(u.Url, u is SiteUrl ? UrlObjectType.SiteUrl : UrlObjectType.UrlRewrite, u.Id, u.IgnoreQueryString);
            };

            var liveUntil = DateTime.Now + Config.DataCacheTtl;

            Log.Info(LogCategory, "Begin site dir cache build");

            var root = new DirectoryNode<UrlObject, UrlObjectNodeMetadata>("site-url-root", true);


            Log.Info(LogCategory, " Begin get rewrites");
            //rewrites
            var rewrites = from r in GetRewrites(siteName, liveUntil)
                           orderby r.Url
                           select r;

            Log.Info(LogCategory, "Begin rewrite build");

            DirectoryNode<UrlObject, UrlObjectNodeMetadata>.BuildCacheFromUrlObjects(root, rewrites, selectName, u => () => u);

            Log.Info(LogCategory, "Begin get urls");

            var urls = from u in GetSiteUrls(siteName, liveUntil)
                       orderby u.Url
                       select u;

            Log.Info(LogCategory, "Begin url build");

            //site urls
            DirectoryNode<UrlObject, UrlObjectNodeMetadata>.BuildCacheFromUrlObjects(root, urls, selectName, u => () => u);

            Log.Info(LogCategory, "End site dir cache build");

            return root;
        }

        public abstract SiteUrl GetSiteUrl(Guid id);
        public abstract RewriteUrl GetRewriteUrl(Guid id);
        public abstract IEnumerable<SiteUrl> GetSiteUrls(string siteName, DateTime? liveUntil);
        public abstract IEnumerable<RewriteUrl> GetRewrites(string siteName, DateTime? liveUntil);
        public abstract IEnumerable<RewriteUrl> GetRewritesForSiteUrl(Guid siteUrlId);
        
        protected abstract RewriteUrl SaveUrlRewriteCore(RewriteUrl toSave);
        protected abstract SiteUrl SaveSiteUrlCore(SiteUrl toSave);
        protected abstract Site SaveSiteCore(Site toSave);
        
        public virtual Site GetSite(string siteName)
        {
            return GetSites().Where(s => s.SiteName == siteName).FirstOrDefault();
        }

        public Lazy<UrlObject> NavigateTo(Uri url)
        {
            var site = GetSites().NavigateToSite(url);

            if (site == null) return null;

            return GetSiteDirectory(site.SiteName).NavigateToItem(url);
        }

    }
}
